/*
 * Huawei HiNIC PCI Express Linux driver
 * Copyright(c) 2017 Huawei Technologies Co., Ltd
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/ethtool.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/module.h>

#include "ossl_knl.h"
#include "hinic3_cqm.h"
#include "hinic3_crm.h"
#include "hinic3_hw.h"
#include "hinic3_common.h"
#include "hinic3_nic.h"
#include "hisec_cmd.h"
#include "hisec_cfg.h"

int hisec_send_msg_to_mpu_sync(void *hwdev, u8 cmd, void *buf_in, u16 in_size, void *buf_out, u16 *out_size)
{
    /* todo:: vf need to mpu com or not ? */
    return hinic3_msg_to_mgmt_sync(hwdev, HINIC3_MOD_CRYPT, cmd, buf_in, in_size, buf_out, out_size, 0,
        HINIC3_CHANNEL_CRYPT);
}

int hisec_reg_cqm_service(void *hwdev, service_register_template_s *reg_temp)
{
    if (!hwdev || (reg_temp->service_type >= SERVICE_T_MAX))
        return -EINVAL;

    return cqm_service_register(hwdev, reg_temp);
}

void hisec_unreg_cqm_service(void *hwdev, u32 service_type)
{
    if (!hwdev || (service_type >= SERVICE_T_MAX))
        return;

    cqm_service_unregister(hwdev, service_type);
}

int hisec_set_ipsec_offload_mode(void *hwdev, u8 offload_mode)
{
    struct hinic3_nic_io *nic_io = NULL;
    struct hisec_cmd_ipsec_offload_mode ipsec_cfg;
    u16 out_size = sizeof(ipsec_cfg);
    int err;

    if (!hwdev)
        return -EINVAL;

    nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);

    memset(&ipsec_cfg, 0, sizeof(ipsec_cfg));
    ipsec_cfg.func_id = hinic3_global_func_id(hwdev);
    ipsec_cfg.offload_mode = offload_mode;

    err = hisec_send_msg_to_mpu_sync(hwdev, HISEC_MPU_CMD_SET_IPSEC_OFFLOAD_MODE, &ipsec_cfg, sizeof(ipsec_cfg),
        &ipsec_cfg, &out_size);
    if (err || !out_size || ipsec_cfg.msg_head.status) {
        nic_err(nic_io->dev_hdl, "Failed to set ipsec offload mode, err: %d, status: 0x%x, out size: 0x%x\n", err,
            ipsec_cfg.msg_head.status, out_size);
        return -EINVAL;
    }

    return 0;
}

int hisec_get_crypto_stats(void *hwdev, struct hisec_crypto_stats *stats)
{
    struct hisec_cmd_cryptodev_stats cdev_stats;
    u16 out_size = sizeof(cdev_stats);
    struct hinic3_nic_io *nic_io = NULL;
    int err;

    if (!hwdev || !stats)
        return -EINVAL;

    nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);
    memset(&cdev_stats, 0, sizeof(cdev_stats));

    err = hisec_send_msg_to_mpu_sync(hwdev, HISEC_MPU_CMD_GET_CRYPTO_STATS,
                                     &cdev_stats, sizeof(cdev_stats),
                                     &cdev_stats, &out_size);
    if (err || !out_size || cdev_stats.msg_head.status) {
        nic_err(nic_io->dev_hdl,
                "Failed to get cryptodev statistics, err: %d, status: 0x%x, out size: 0x%x\n",
                err, cdev_stats.msg_head.status, out_size);
        return -EFAULT;
    }

    memcpy(stats, &cdev_stats.stats, sizeof(*stats));

    return 0;
}

int hisec_set_ipsec_policy(void *hwdev, hisec_cmd_set_ipsec_sp_s *sp_msg)
{
    struct hinic3_nic_io *nic_io = NULL;
    struct hinic3_cmd_buf *cmd_buf = NULL;
    hisec_cmd_set_ipsec_sp_s *sp_msg_buf = NULL;
    int ret = 0;
    u64 out_param = 0;

    if (!hwdev || !sp_msg)
        return -EINVAL;

    nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);

    cmd_buf = hinic3_alloc_cmd_buf(hwdev);
    if (!cmd_buf) {
        sdk_err(nic_io->dev_hdl, "Failed to alloc cmd buf\n");
        return -ENOMEM;
    }

    cmd_buf->size = sizeof(*sp_msg_buf);
    sp_msg_buf = (hisec_cmd_set_ipsec_sp_s *)cmd_buf->buf;
    memcpy(sp_msg_buf, sp_msg, sizeof(hisec_cmd_set_ipsec_sp_s));

    ret = hinic3_cmdq_direct_resp(hwdev, HINIC3_MOD_CRYPT, HISEC_NPU_CMD_SET_IPSEC_SP, cmd_buf,
        &out_param, 0, HINIC3_CHANNEL_CRYPT);
    if (ret || out_param) {
        sdk_err(nic_io->dev_hdl, "Failed to set IPsec policy, ret: %d, out_param: 0x%llx\n", ret, out_param);
        ret = -EFAULT;
    }

    hinic3_free_cmd_buf(hwdev, cmd_buf);

    return ret;
}

int hisec_set_ipsec_sa(void *hwdev, hisec_cmd_set_ipsec_sa_s *sa_msg)
{
    struct hinic3_nic_io *nic_io = NULL;
    struct hinic3_cmd_buf *cmd_buf = NULL;
    hisec_cmd_set_ipsec_sa_s *sa_msg_buf = NULL;
    int ret = 0;
    u64 out_param = 0;

    if (!hwdev || !sa_msg)
        return -EINVAL;

    nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);

    cmd_buf = hinic3_alloc_cmd_buf(hwdev);
    if (!cmd_buf) {
        sdk_err(nic_io->dev_hdl, "Failed to alloc cmd buf\n");
        return -ENOMEM;
    }

    cmd_buf->size = sizeof(*sa_msg_buf);
    sa_msg_buf = (hisec_cmd_set_ipsec_sa_s *)cmd_buf->buf;
    memcpy(sa_msg_buf, sa_msg, sizeof(*sa_msg));

    ret = hinic3_cmdq_direct_resp(hwdev, HINIC3_MOD_CRYPT,
    				  HISEC_NPU_CMD_SET_IPSEC_SA, cmd_buf,
        			  &out_param, 0, HINIC3_CHANNEL_CRYPT);
    if (ret || out_param) {
        sdk_err(nic_io->dev_hdl, "Failed to set IPsec SA context, ret: %d, out_param: 0x%llx\n",
		ret, out_param);
        ret = -EFAULT;
    }

    hinic3_free_cmd_buf(hwdev, cmd_buf);

    return ret;
}

int hisec_flush_ipsec_resource(void *hwdev, u32 flush_flag)
{
    struct hinic3_nic_io *nic_io = NULL;
    struct hinic3_cmd_buf *cmd_buf = NULL;
    hisec_cmd_flush_ipsec_res_s *flush_msg_buf = NULL;
    int ret = 0;
    u64 out_param = 0;

    if (!hwdev)
        return -EINVAL;

    nic_io = hinic3_get_service_adapter(hwdev, SERVICE_T_NIC);

    cmd_buf = hinic3_alloc_cmd_buf(hwdev);
    if (!cmd_buf) {
        sdk_err(nic_io->dev_hdl, "Failed to alloc cmd buf\n");
        return -ENOMEM;
    }

    cmd_buf->size = sizeof(*flush_msg_buf);
    flush_msg_buf = (hisec_cmd_flush_ipsec_res_s *)cmd_buf->buf;
    flush_msg_buf->func_id = cpu_to_be16(hinic3_global_func_id(hwdev));
    flush_msg_buf->flush_flag = cpu_to_be32(flush_flag);
    flush_msg_buf->cmdhdr.dw0.bs.cmd_type = HISEC_NPU_CMD_FLUSH_IPSEC_RES;
    flush_msg_buf->cmdhdr.dw0.value = cpu_to_be32(flush_msg_buf->cmdhdr.dw0.value);

    ret = hinic3_cmdq_direct_resp(hwdev, HINIC3_MOD_CRYPT,
    				  HISEC_NPU_CMD_FLUSH_IPSEC_RES, cmd_buf,
    				  &out_param, 0, HINIC3_CHANNEL_CRYPT);
    if (ret || out_param) {
        sdk_err(nic_io->dev_hdl, "Failed to flush IPsec res, ret: %d, out_param: 0x%llx\n",
		ret, out_param);
        ret = -EFAULT;
    }

    hinic3_free_cmd_buf(hwdev, cmd_buf);

    return ret;
}
